package shlib

import (
	"bytes"
	"crypto/ed25519"
	"crypto/rand"
	"errors"
	"github.com/agl/ed25519/extra25519"
	"golang.org/x/crypto/curve25519"
	//"github.com/agl/ed25519"
	//"github.com/agl/ed25519/extra25519"
	//"golang.org/x/crypto/curve25519"
)

//=============================================================================

/**
 * 包装过的 Ed25519 公钥对象。
 * @inherits IPublicKey, IEcPublicKey
 */
type Ed25519PublicKey [32]byte

/**
 * @implements IKey.Algo
 */
func (this *Ed25519PublicKey) Algo() string {
	return "ED25519"
}

/**
 * @implements IKey.Type
 */
func (this *Ed25519PublicKey) Type() string {
	return KeyType_Public
}

/**
 * @implements IKey.ExportX
 */
func (this *Ed25519PublicKey) ExportToXBuf(xb *XBuf) {
	xb.PutVStr(this.Algo())
	xb.PutVStr(this.Type())
	xb.PutBytes(this[:])
}

/**
 * @implements IKey.Export
 */
func (this *Ed25519PublicKey) Export() []byte {
	xb := NewXBuf(len(this) + 32)
	this.ExportToXBuf(xb)

	return xb.Bytes()
}

/**
 * @implements IKey.ExportTo
 */
func (this *Ed25519PublicKey) ExportTo(dst []byte) int {
	xb := XBuf{dst, 0, 0, false}
	this.ExportToXBuf(&xb)

	return xb.Wi
}

/**
 * @implements IKey.Equals
 */
func (this *Ed25519PublicKey) Equals(other IKey) bool {
	otherKey, ok := other.(*Ed25519PublicKey)
	if !ok {
		return false
	}
	return bytes.Equal(this[:], otherKey[:])
}

/**
 * @implements IPublicKey.Verify
 */
func (this *Ed25519PublicKey) Verify(data []byte, signature []byte) bool {
	if len(signature) != 64 {
		return false
	}

	return ed25519.Verify(this[:], data, signature)
}

//=============================================================================

/**
 * 包装过的私钥对象。
 * @inherits IPrivateKey, IEcPrivateKey
 */
type Ed25519PrivateKey [64]byte

/**
 * @implements IKey.Algo
 */
func (this *Ed25519PrivateKey) Algo() string {
	return "ED25519"
}

/**
* @implements IKey.Type
 */
func (this *Ed25519PrivateKey) Type() string {
	return KeyType_Private
}

/**
 * @implement IKey.ExportToXBuf
 */
func (this *Ed25519PrivateKey) ExportToXBuf(xb *XBuf) {
	xb.PutVStr(this.Algo())
	xb.PutVStr(this.Type())
	xb.PutBytes(this[:])
}

/**
 * @implements IKey.Export
 */
func (this *Ed25519PrivateKey) Export() []byte {
	xb := NewXBuf(len(this) + 32)
	this.ExportToXBuf(xb)

	return xb.Bytes()
}

/**
 * @implements IKey.ExportTo
 */
func (this *Ed25519PrivateKey) ExportTo(dst []byte) int {
	xb := XBuf{dst, 0, 0, false}
	this.ExportToXBuf(&xb)

	return xb.Wi
}

/**
 * @implements IKey.Equals
 */
func (this *Ed25519PrivateKey) Equals(other IKey) bool {
	otherkey, ok := other.(*Ed25519PrivateKey)
	if !ok {
		return false
	}

	return bytes.Equal(this[:], otherkey[:])
}

/**
 * @implements IPrivateKey.Public
 */
func (this *Ed25519PrivateKey) Public() IPublicKey {
	pk := new(Ed25519PublicKey)
	copy(pk[:], this[32:])

	return pk
}

/**
 * @implements IPrivateKey.Sign
 */
func (this *Ed25519PrivateKey) Sign(data []byte) []byte {
	return ed25519.Sign(this[:], data)
}

/**
 * @implements IEcPrivateKey.RecoverKey
 */
func (this *Ed25519PrivateKey) RecoverKey(pk IPublicKey, sk ISecretKey) ([]byte, error) {
	pkey, ok := pk.(*Ed25519PublicKey)
	if !ok {
		return nil, errors.New("incorrect key algorithm")
	}

	curvePublic := new([32]byte)
	extra25519.PublicKeyToCurve25519(curvePublic, (*[32]byte)(pkey))
	curvePrivate := new([32]byte)
	extra25519.PrivateKeyToCurve25519(curvePrivate, (*[64]byte)(this))
	data := new([32]byte)

	curve25519.ScalarMult(data, curvePrivate, curvePublic)

	return sk.Encrypt(data[:]), nil
}

//=============================================================================

/**
 * 根据指定类型创建相应密钥对象。
 */
func Ed25519KeyFactory(keyType string, keyData []byte) (IKey, int, error) {
	switch keyType {
	case KeyType_Public:
		// 生成一个公钥
		if len(keyData) < 32 {
			return nil, 0, errKeyData
		}

		key := new(Ed25519PublicKey)
		copy(key[:], keyData[:32])

		return key, 32, nil

	case KeyType_Private:
		// 生成一个私钥
		if len(keyData) < 64 {
			return nil, 0, errKeyData
		}

		key := &Ed25519PrivateKey{}
		copy(key[:], keyData[:64])

		return key, 64, nil
	}

	return nil, 0, errKeyType(keyType)
}

/**
 * 用默认曲线设置生成 ECC 公私钥对。
 */
func Ed25519KeyGenerator() (IKey, error) {
	_, k, _ := ed25519.GenerateKey(rand.Reader)
	sk := &Ed25519PrivateKey{}
	copy(sk[:], k[:])

	return sk, nil
}
